/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       scrollbar.cpp

	$Header: /game/scrollbar.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "scrollbar.h"

#include "adventure_sounds.h"
#include "bitmap_layer_window.h"
#include "bound_handler.h"
#include "button.h"
#include "button_bitmaps.h"
#include "button_cache.h"
#include "mouse_event.h"
#include "pixel_24.h"
#include "platform.h"
#include "scrollbar_bitmaps.h"
#include "scrollbar_cache.h"

t_scrollbar_cache g_vertical_scroll( "vertical_scroll" );
t_scrollbar_cache g_horizontal_scroll( "horizontal_scroll" );

namespace
{
	// delay for when a button is pressed and held down
	const int k_initial_button_delay = 500;
	const int k_subsequent_button_delay = 10;
	enum { k_scroll_none_button = -1, k_scroll_up_button, k_scroll_down_button };
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
// create a scrollbar, "size" pixels wide or tall, with default images.
t_scrollbar::t_scrollbar( t_screen_point point, int size, t_window* parent, int min_value, 
						  int max_value, bool vertical )
           : t_window( k_no_transparency, parent ), 
		     t_idle_processor( 1, k_initial_button_delay ),
			 m_button_pressed( k_scroll_none_button )
{
	if (vertical)
		init( point, size, min_value, max_value, vertical, g_vertical_scroll.get() );
	else
		init( point, size, min_value, max_value, vertical, g_horizontal_scroll.get() );
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
// create a scrollbar, "size" pixels wide or tall, with specified images.
t_scrollbar::t_scrollbar( t_screen_point point, int size, t_window* parent, int min_value, 
						  int max_value, bool vertical,
				          t_cached_ptr<t_scrollbar_bitmaps>& bitmaps )
           : t_window( k_no_transparency, parent ), 
		     t_idle_processor( 1, k_initial_button_delay ),
		     m_button_pressed( k_scroll_none_button )
{
	init( point, size, min_value, max_value, vertical, bitmaps );
}
		        
// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::left_button_down( t_mouse_event const& event )
{
	// are we in the scrollbar area at all?
	if (!is_point_in_rect( event.client_point, m_bar_rect ))
		return;

	// do we have a thumb?
	if (m_thumb == 0)
		return;

	// are we touching the thumb?
	if (m_thumb->is_contained( m_thumb->to_client( event.screen_point ) ))
	{
		m_dragging = true;
		m_drag_start = event.client_point;
		return;
	}

	// page up / page down
	if (m_vertical)
	{
		if (event.screen_point.y < m_thumb->get_screen_rect().top)
 			set_position( m_value - m_page_size );
		else
			set_position( m_value + m_page_size );
	}
	else
	{
		if (event.screen_point.x < m_thumb->get_screen_rect().left)
 			set_position( m_value - m_page_size );
		else
			set_position( m_value + m_page_size );
	}
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::left_button_up( t_mouse_event const& event )
{
	if (m_dragging)
		drag( event.client_point );
	m_dragging = false;
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::mouse_leaving( t_window* old_window, t_window* new_window,
								 t_mouse_event const& event )
{
	drag( event.client_point );

	// This just doesn't work well. Mouse should not have to be in client area
	// to be able to drag the slider

	/*
	if (!m_dragging)
		return;

	if (is_point_in_rect( event.client_point, m_bar_rect ))
		return;

	drag( event.client_point );
	m_dragging = false;
	*/
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::mouse_move( t_mouse_event const& event )
{
	if (m_dragging)
	{
		if (!event.left_button) // we somehow lost the mouse_up event.  Abort.
		{
			m_dragging = false;
			return;
		}
		if (event.client_point != get_mouse_position( this ))
			return;
		// are we in the scrollbar area at all?
		if (!is_point_in_rect( event.client_point, m_bar_rect ))
			return;
		drag( event.client_point );
	}
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::set_limits( int min_value, int max_value )
{
	m_min_value = min_value;
	m_max_value = max_value;
	if (m_max_value < min_value)
		m_max_value = min_value;
	set_position( m_value );
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::set_position( int arg )
{
	int old_value = m_value;

	if (arg < m_min_value)
		arg = m_min_value;
	if (arg > m_max_value)
		arg = m_max_value;
	m_value = arg;
	if (m_thumb != 0)
	{
		t_screen_point point = to_client( m_thumb->get_screen_rect().top_left() );
		int            range = m_max_value - m_min_value;

		if (m_vertical)
		{
			int height = m_bar_rect.height() - m_thumb->get_height();

			if (height <= 0)
				return;
			if (range > 0)
				point.y = ((arg - m_min_value) * height + range / 2 ) / range + m_bar_rect.top;
			else
				point.y = m_bar_rect.top;
		}
		else
		{
			int width = m_bar_rect.width() - m_thumb->get_width();

			if (width <= 0)
				return;
			if (range > 0)
				point.x = ((arg - m_min_value) * width + range / 2 ) / range + m_bar_rect.left;
			else
				point.x = m_bar_rect.left;
		}
		m_thumb->move( m_thumb->get_client_rect() + point );
	}
	if (old_value != m_value)
		m_handler( this, m_value );
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::click_up( t_button* )
{
	if (is_active())
		suspend_idle_processing();
	m_button_pressed = k_scroll_none_button;
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::up_button_down_clicked( t_button* )
{
	if (!is_active())
	{
		set_delay(k_initial_button_delay);
		resume_idle_processing();
	}
	set_position( m_value - 1 );
	m_button_pressed = k_scroll_up_button;
	get_dialog_sound( k_dialog_sound_button )->play( get_sound_volume() );
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::click_down( t_button* )
{
	if (is_active())
		suspend_idle_processing();
	m_button_pressed = k_scroll_none_button;
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::down_button_down_clicked( t_button* )
{
	if (!is_active())
	{
		set_delay(k_initial_button_delay);
		resume_idle_processing();
	}
	set_position( m_value + 1 );
	m_button_pressed = k_scroll_down_button;
	get_dialog_sound( k_dialog_sound_button )->play( get_sound_volume() );
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::drag( t_screen_point point )
{
	if (!m_dragging)
		return;

	t_screen_point offset = point - m_drag_start;
	t_screen_point thumb_point = to_client( m_thumb->get_screen_rect().top_left() );
	t_screen_point new_point = thumb_point + offset;
	int            value;
	int            range = m_max_value - m_min_value;

	if (range <= 0)
		return;

	if (m_vertical)
	{
		int height = m_bar_rect.height() - m_thumb->get_height();

		if (height <= 0)
			return;

		new_point.y -= m_bar_rect.top;
		if (new_point.y < 0)
			new_point.y = 0;
		if (new_point.y > height)
			new_point.y = height;
		value = (new_point.y * range + height / 2) / height + m_min_value;
	}
	else
	{
		int width = m_bar_rect.width() - m_thumb->get_width();

		if (width <= 0)
			return;

		new_point.x -= m_bar_rect.left;
		if (new_point.x < 0)
			new_point.x = 0;
		if (new_point.x > width)
			new_point.x = width;
		value = (new_point.x * range + width / 2) / width + m_min_value;
	}
	set_position( value );
	m_drag_start += to_client( m_thumb->get_screen_rect().top_left() ) - thumb_point;
}

// ------------------------------------------------------------------
// set button images
// ------------------------------------------------------------------
static void set_button_images( t_button* button, t_bitmap_layer const* released_image,
							   t_bitmap_layer const* pressed_image,
							   t_screen_point& origin )
{
	// offset both images to make the box corner (0,0)
	t_screen_rect  rect = get_extent( released_image->get_rect(), pressed_image->get_rect() );
	
	origin = rect.top_left();
	button->set_released_image( new t_bitmap_layer_window( released_image, -origin, button ));
	button->set_pressed_image( new t_bitmap_layer_window( pressed_image, -origin, button ));
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::init( t_screen_point point, int size, int min_value, int max_value,
					    bool vertical, t_cached_ptr<t_scrollbar_bitmaps>& bitmaps )
{
	t_screen_rect rect;
	t_bitmap_layer const* thumb_bitmap = bitmaps->get_thumb();
	t_bitmap_layer const* background_bitmap = bitmaps->get_background();

	// hang on to bitmap group.
	m_bitmaps = bitmaps;
	m_dragging = false;

	t_button* up_button = new t_button( t_screen_point(0,0), this );
	t_button* down_button = new t_button( t_screen_point(0,0), this );

	// add images to up and down buttons
	t_screen_point up_origin;
	t_screen_point down_origin;

	set_button_images( up_button, bitmaps->get_up_released(), bitmaps->get_up_pressed(),
		               up_origin );
	set_button_images( down_button, bitmaps->get_down_released(), 
		               bitmaps->get_down_pressed(), down_origin );

	// add callbacks to up and down buttons.
	up_button->set_click_handler( bound_handler( *this, &t_scrollbar::click_up ) );
	up_button->set_down_click_handler( bound_handler( *this, &t_scrollbar::up_button_down_clicked ) );
	down_button->set_click_handler( bound_handler( *this, &t_scrollbar::click_down ) );
	down_button->set_down_click_handler( bound_handler( *this, &t_scrollbar::down_button_down_clicked ) );

	// determine if we need to behave like a transparent window.
	// true if any child has transparency, or the sizes are different.
	if (up_button->get_transparency() != k_no_transparency
		|| down_button->get_transparency() != k_no_transparency
		|| background_bitmap->get_alpha_depth() > 0)
		m_has_transparency = k_completely_transparent;

	m_min_value = min_value;
	m_max_value = max_value;
	if (m_max_value < m_min_value)
		m_max_value = m_min_value;
	m_value = m_min_value;
	m_vertical = vertical;
	m_page_size = (m_max_value - m_min_value ) / 5;

	t_screen_point origin = background_bitmap->get_rect().top_left();

	// move up button
	up_button->move( up_origin );
	if (vertical)
	{
		// calculate width of scrollbar.
		int width = to_client( up_button->get_screen_rect() ).right;
		int new_width = to_client( down_button->get_screen_rect() ).right;

		if (new_width > width)
			width = new_width;
		new_width = background_bitmap->get_rect().right;
		if (new_width > width)
			width = new_width;
		m_has_transparency = k_completely_transparent;

		// compute overall size of scrollbar.
		rect = t_screen_rect( point.x, point.y, point.x + width, point.y + size );
		// calculate client area (area where thumb can appear)
		m_bar_rect.left = origin.x;
		m_bar_rect.top = up_button->get_height();
		m_bar_rect.right = background_bitmap->get_rect().right;
		m_bar_rect.bottom = rect.height() - down_button->get_height();

		// create background windows
		t_screen_point pt(0,0);
		int            height = m_bar_rect.height();
		t_window*      background = new t_window( m_bar_rect, k_completely_transparent, this );
		t_window*      child;
		while (pt.y < height)
		{
			child = new t_bitmap_layer_window( background_bitmap, pt - origin, background );
			pt.y += background_bitmap->get_height();
		}

		// create thumb
		if (thumb_bitmap && m_bar_rect.height() >= thumb_bitmap->get_height())
			m_thumb = new t_bitmap_layer_window( thumb_bitmap, m_bar_rect.top_left() - origin, this );

		// move down button to bottom
		pt.x = down_origin.x;
		pt.y = m_bar_rect.bottom;
		down_button->move( pt );
	}
	else
	{
		// calculate width of scrollbar.
		int height = up_button->get_height();
		int new_height = down_button->get_height();

		if (new_height > height)
			height = new_height;

		new_height = background_bitmap->get_height();
		m_has_transparency = k_completely_transparent;
		if (new_height > height)
			height = new_height;

		// compute overall size of scrollbar.
		rect = t_screen_rect( point.x, point.y, point.x + size, point.y + height );
		// calculate client area (area where thumb can appear)
		m_bar_rect.left = up_button->get_width();
		m_bar_rect.top = origin.y;
		m_bar_rect.right = rect.width() - down_button->get_width();
		m_bar_rect.bottom = background_bitmap->get_rect().bottom;

		// create background windows
		t_screen_point pt(0,0);
		int            width = m_bar_rect.width();
		t_window*      background = new t_window( m_bar_rect, k_completely_transparent, this );
		t_window*      child;
		while (pt.x < width)
		{
			child = new t_bitmap_layer_window( background_bitmap, pt - origin, background );
			pt.x += background_bitmap->get_width();
		}

		// create thumb
		if (thumb_bitmap && m_bar_rect.width() >= thumb_bitmap->get_width())
			m_thumb = new t_bitmap_layer_window( thumb_bitmap, m_bar_rect.top_left() - origin, this );

		// move down button to bottom
		pt.x = m_bar_rect.right;
		pt.y = down_origin.y;
		down_button->move( pt );
	}
	up_button->move_to_front();
	down_button->move_to_front();
	t_window::init( rect );
}

// ------------------------------------------------------------------
// scrollbar class
// ------------------------------------------------------------------
void t_scrollbar::on_idle()
{
	// set the next delay to a shorter time for button being held down
	set_delay( k_subsequent_button_delay );

	if( m_button_pressed == k_scroll_up_button )
	{
		set_position( m_value - 1 );
	} else
	if( m_button_pressed == k_scroll_down_button )
	{
		set_position( m_value + 1 );
	}
}





